#include "mtm_ex4.h"
#include "mtmtest.h"
#include "list.h"

namespace listTest {

/* #################### Auxiliary classes and functions ################### */

/**
 * Function object for finding a target integer
 */
class EqualTo {
private:
	int target;
public:
	EqualTo(int i) :
			target(i) {
	}
	bool operator()(const int& i) const {
		return i == target;
	}
};

/**
 * Function object to sort integer list in a descending order
 */
class GreaterThan {
public:
	bool operator()(const int& left, const int& right) {
		return left > right;
	}
};

template<class T, int N>
void populateList(List<T>& list, const T items[N]) {
	for (int i = 0; i < N; i++) {
		list.insert(items[i]);
	}
}

/* ########################## Tests ######################### */

void ctorTest() {
	List<int> list;

	ASSERT_EQUALS(0, list.getSize());
	ASSERT_TRUE(list.begin() == list.end());
	ASSERT_THROWS(mtm::ELEMENT_NOT_FOUND, list.remove(list.begin()));
}

void copyCtorTest() {
	List<int> list;
	const int numbers[] = { 10, 20, 5 };
	populateList<int, 3>(list, numbers);

	List<int> emptyList, emptyListCopy(emptyList);
	ASSERT_EQUALS(0, emptyListCopy.getSize());

	List<int> copiedList(list);
	ASSERT_EQUALS(3, copiedList.getSize());
	ASSERT_TRUE(list == copiedList);

	const List<int> copiedConstList(list);
	ASSERT_EQUALS(3, copiedList.getSize());
	ASSERT_TRUE(list == copiedList);
}

void assignmentTest() {
	List<int> list;
	const int numbers[] = { 10, 20, 5 };
	populateList<int, 3>(list, numbers);

	List<int> list2(list);
	List<int> list3;
	list3 = list2;
	ASSERT_TRUE(list3 == list);

	List<int> emptyList1;
	List<int> emptyList2;
	emptyList2 = emptyList1;
	ASSERT_FALSE(&emptyList1 == &emptyList2);
	ASSERT_TRUE(emptyList1 == emptyList2);
}

void comparisonTest() {
	List<int> emptyList1, emptyList2, populatedList1;
	const int numbers[] = { 10, 20, 5 };
	populateList<int, 3>(populatedList1, numbers);
	List<int> populatedList2(populatedList1);

	ASSERT_TRUE(emptyList1 == emptyList1);
	ASSERT_TRUE(emptyList1 == emptyList2);
	ASSERT_TRUE(emptyList1 != populatedList2);
	ASSERT_TRUE(populatedList1 == populatedList1);
	ASSERT_TRUE(populatedList1 == populatedList2);
	ASSERT_FALSE(populatedList1 != populatedList1);
	ASSERT_FALSE(emptyList1 != emptyList1);
}

void beginTest() {
	List<int> populatedList;
	const int numbers[] = { 10, 20, 5 };
	populateList<int, 3>(populatedList, numbers);
	const List<int> constPopulatedList(populatedList);
	List<int>::Iterator testIterator(populatedList.begin());
	List<int>::ConstIterator constTestIterator(constPopulatedList.begin());

	ASSERT_EQUALS(10, *testIterator);
	ASSERT_EQUALS(10, *constTestIterator);

	++testIterator;
	++constTestIterator;
	ASSERT_EQUALS(20, *testIterator);
	ASSERT_EQUALS(20, *constTestIterator);

	++testIterator;
	++constTestIterator;
	ASSERT_EQUALS(5, *testIterator);
	ASSERT_EQUALS(5, *constTestIterator);
}

void endTest() {
	List<int> list;
	const int numbers[] = { 4, 5, 6 };
	populateList<int, 3>(list, numbers);
	const List<int> constList(list);

	int counter = 0;
	for (auto i = list.begin(); i != list.end(); ++i) {
		++counter;
	}
	ASSERT_EQUALS(3, counter);

	counter = 0;
	for (auto i = constList.begin(); i != constList.end(); ++i) {
		++counter;
	}

	ASSERT_EQUALS(3, counter);
}

void insertTest() {
	List<int> list;

	// check first item insert
	list.insert(1);
	ASSERT_EQUALS(1, list.getSize());
	ASSERT_EQUALS(1, *list.begin());

	// check insertion of multiple items to end of list
	list.insert(2);
	list.insert(3);
	ASSERT_EQUALS(3, list.getSize());
	auto iterator = list.begin();
	ASSERT_EQUALS(1, *iterator);
	ASSERT_EQUALS(2, *++iterator);
	ASSERT_EQUALS(3, *++iterator);

	// check insertion of item to beginning of list
	list.insert(0, list.begin());
	ASSERT_EQUALS(4, list.getSize());
	ASSERT_EQUALS(0, *list.begin());

	// check insertion of items to middle of list,
	// and that iterator points to same item after insert
	iterator = ++++++list.begin();
	ASSERT_EQUALS(3, *iterator);
	list.insert(21, iterator);
	list.insert(22, iterator);
	ASSERT_EQUALS(3, *iterator);
	ASSERT_EQUALS(6, list.getSize());
	iterator = list.begin();
	ASSERT_EQUALS(0, *iterator);
	ASSERT_EQUALS(1, *++iterator);
	ASSERT_EQUALS(2, *++iterator);
	ASSERT_EQUALS(21, *++iterator);
	ASSERT_EQUALS(22, *++iterator);
	ASSERT_EQUALS(3, *++iterator);

	// check insert with iterator of different list
	List<int> list2;
	ASSERT_THROWS(mtm::NON_MATCHING_LIST, list.insert(10, list2.begin()));
}

void removeTest() {
	List<double> list;
	const double numbers[] = { 756.34, 124.23, 212.23 };
	populateList<double, 3>(list, numbers);

	// check iterator is of the same list
	List<double> tempList1;
	ASSERT_THROWS(mtm::NON_MATCHING_LIST, list.remove(tempList1.begin()));

	// check only wanted item is removed
	List<double>::Iterator listIterator1 = list.begin();
	ASSERT_EQUALS(3, list.getSize());

	list.remove(++listIterator1);

	ASSERT_EQUALS(2, list.getSize());

	List<double>::Iterator listIterator2 = list.begin();

	ASSERT_EQUALS(756.34, *(listIterator2++));
	ASSERT_EQUALS(212.23, *listIterator2);

	ASSERT_NO_THROW(list.remove(listIterator2));
	ASSERT_EQUALS(1, list.getSize());

	List<double>::Iterator listIterator3 = list.begin();
	ASSERT_EQUALS(756.34, *listIterator3);
	list.remove(listIterator3);

	// check exception is thrown when removing from empty list,
	// or iterator points to end of list
	ASSERT_THROWS(mtm::ELEMENT_NOT_FOUND, list.remove(list.begin()));
	ASSERT_THROWS(mtm::ELEMENT_NOT_FOUND, list.remove(list.end()));
}

void findTest() {
	List<int> list;
	const int numbers[] = { 1, 3, 2, 3, 4 };
	populateList<int, 5>(list, numbers);
	List<int>::Iterator iterator = list.find(EqualTo(3));

	// find the list item (==3)
	ASSERT_EQUALS(3, *iterator);

	// verify the iterator points to the *first* occurrence of 3
	ASSERT_EQUALS(2, *++iterator);

	// try to find item that doesn't exist
	iterator = list.find(EqualTo(5));
	ASSERT_TRUE(iterator == list.end());

	// check again for constant list
	const List<int> constList(list);
	List<int>::ConstIterator constIterator = constList.find(EqualTo(3));
	ASSERT_EQUALS(3, *constIterator);
	ASSERT_EQUALS(2, *++constIterator);
	constIterator = constList.find(EqualTo(5));
	ASSERT_TRUE(constIterator == constList.end());
}

void sortTest() {
	List<int> list, sortedList;
	const int numbers[] = { 1, 3, 2, 3, 4, 5, 2 }, sortedNumbers[] = { 5, 4, 3,
			3, 2, 2, 1 };
	populateList<int, 7>(list, numbers);
	populateList<int, 7>(sortedList, sortedNumbers);
	list.sort(GreaterThan());
	ASSERT_TRUE(list == sortedList);
}

void getSizeTest() {
	List<string> list;

	ASSERT_EQUALS(0, list.getSize());

	list.insert("This");
	ASSERT_EQUALS(1, list.getSize());

	list.insert("Is");
	ASSERT_EQUALS(2, list.getSize());

	list.insert("A Test");
	ASSERT_EQUALS(3, list.getSize());

	list.remove(list.begin());
	ASSERT_EQUALS(2, list.getSize());
}

}

/* uncomment to run tests, or copy the relevant code */
//#include <set>
//#include "Store.h"
//#include "Order.h"
//using std::set;
//using namespace listTest;
//
//int main() {
//	RUN_TEST(ctorTest);
//	RUN_TEST(copyCtorTest);
//	RUN_TEST(assignmentTest);
//	RUN_TEST(comparisonTest);
//	RUN_TEST(beginTest);
//	RUN_TEST(endTest);
//	RUN_TEST(insertTest);
//	RUN_TEST(removeTest);
//	RUN_TEST(findTest);
//	RUN_TEST(sortTest);
//	RUN_TEST(getSizeTest);
//	return 0;
//}
